package com.sunyard.utils.wxv3Util;

import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;

import java.io.IOException;
import java.net.URI;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;

/**
 * @author : ziliang.zhou
 * @version : 1.0
 * @date : 2020-09-15 16:44
 * @description :
 **/
@Slf4j
@Component
public class SignatureUtil {

    private static final String SYMBOLS =
            "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private static final SecureRandom RANDOM = new SecureRandom();

    private static final String SCHEME = "WECHATPAY2-SHA256-RSA2048 ";

    protected String generateNonceStr() {
        char[] nonceChars = new char[32];
        for (int index = 0; index < nonceChars.length; ++index) {
            nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
        }
        return new String(nonceChars);
    }


    protected long generateTimestamp() {
        return System.currentTimeMillis() / 1000;
    }


    /**
     * 生成v3请求头信息
     *
     * @param request                 请求url
     * @param privateKey              私钥
     * @param merchantId              微信商户号
     * @param certificateSerialNumber 商户API证书序列号serial_no，用于声明所使用的证书
     * @return
     * @throws IOException
     */
    public String getAuthorization(HttpUriRequest request, PrivateKey privateKey, String merchantId, String certificateSerialNumber) {
        try {
            String nonceStr = generateNonceStr();
            long timestamp = generateTimestamp();
            String message = buildMessage(nonceStr, timestamp, request);
            log.debug("authorization message=[{}]", message);
            String sign = sign(message.getBytes("utf-8"), privateKey);
            String token = "mchid=\"" + merchantId + "\","
                    + "nonce_str=\"" + nonceStr + "\","
                    + "timestamp=\"" + timestamp + "\","
                    + "serial_no=\"" + certificateSerialNumber + "\","
                    + "signature=\"" + sign + "\"";
            log.debug("authorization token=[{}]", token);
            String authorization = SCHEME + token;
            log.debug("authorization [{}]", authorization);
            return authorization;
        } catch (Exception e) {
            log.error("Exception", e);
            throw new RuntimeException(e);
        }

    }

    /**
     * 生成v3请求头信息
     *
     * @param request                 请求url
     * @param privateKey              私钥
     * @param merchantId              微信商户号
     * @param certificateSerialNumber 商户API证书序列号serial_no，用于声明所使用的证书
     * @return
     * @throws IOException
     */
    public String getAuthorization(HttpUriRequest request, String privateKey, String merchantId, String certificateSerialNumber) {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            byte[] decode = Base64Utils.decode(privateKey.getBytes());
            PrivateKey key = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(decode));
            String nonceStr = generateNonceStr();
            long timestamp = generateTimestamp();
            String message = buildMessage(nonceStr, timestamp, request);
            log.debug("authorization message=[{}]", message);
            String sign = sign(message.getBytes("utf-8"), key);
            String token = "mchid=\"" + merchantId + "\","
                    + "nonce_str=\"" + nonceStr + "\","
                    + "timestamp=\"" + timestamp + "\","
                    + "serial_no=\"" + certificateSerialNumber + "\","
                    + "signature=\"" + sign + "\"";
            log.debug("authorization token=[{}]", token);
            String authorization = SCHEME + token;
            log.debug("authorization [{}]", authorization);
            return authorization;
        } catch (NoSuchAlgorithmException e) {
            log.error("NoSuchAlgorithmException", e);
            throw new RuntimeException(e);
        } catch (Exception e) {
            log.error("Exception", e);
            throw new RuntimeException(e);
        }
    }


    /**
     * 生成v3请求头信息
     *
     * @param requestUrl              请求url
     * @param privateKey              私钥
     * @param merchantId              微信商户号
     * @param certificateSerialNumber 商户API证书序列号serial_no，用于声明所使用的证书
     * @param httpMethod              请求方式： GET/POST
     * @return
     * @throws IOException
     */
    public String getAuthorization(String requestUrl, String privateKey, String merchantId, String certificateSerialNumber, String httpMethod) {
        HttpUriRequest request = null;
        try {
            if (httpMethod.equalsIgnoreCase("GET")) {
                request = new HttpGet(requestUrl);
            } else if (httpMethod.equalsIgnoreCase("POST")) {
                request = new HttpPost(requestUrl);
            }
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            byte[] decode = Base64Utils.decode(privateKey.getBytes());
            PrivateKey key = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(decode));
            String nonceStr = generateNonceStr();
            long timestamp = generateTimestamp();
            String message = buildMessage(nonceStr, timestamp, request);
            log.debug("authorization message=[{}]", message);
            String sign = sign(message.getBytes("utf-8"), key);
            String token = "mchid=\"" + merchantId + "\","
                    + "nonce_str=\"" + nonceStr + "\","
                    + "timestamp=\"" + timestamp + "\","
                    + "serial_no=\"" + certificateSerialNumber + "\","
                    + "signature=\"" + sign + "\"";
            log.debug("authorization token=[{}]", token);
            String authorization = SCHEME + token;
            log.debug("authorization [{}]", authorization);
            return authorization;
        } catch (NoSuchAlgorithmException e) {
            log.error("NoSuchAlgorithmException", e);
            throw new RuntimeException(e);
        } catch (Exception e) {
            log.error("Exception", e);
            throw new RuntimeException(e);
        }

    }


    /**
     * SHA256withRSA 签名
     *
     * @param message
     * @param privateKey
     * @return
     */
    private String sign(byte[] message, PrivateKey privateKey) {
        try {
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(privateKey);
            sign.update(message);
            return Base64.getEncoder().encodeToString(sign.sign());
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持SHA256withRSA", e);
        } catch (InvalidKeyException e) {
            throw new RuntimeException("签名计算失败", e);
        } catch (SignatureException e) {
            throw new RuntimeException("无效的私钥", e);
        }


    }


    protected final String buildMessage(String nonce, long timestamp, HttpUriRequest request)
            throws IOException {
        URI uri = request.getURI();
        String canonicalUrl = uri.getRawPath();
        if (uri.getQuery() != null) {
            canonicalUrl += "?" + uri.getRawQuery();
        }

        String body = "";
        // PATCH,POST,PUT
        if (request instanceof HttpEntityEnclosingRequestBase) {
            body = EntityUtils.toString(((HttpEntityEnclosingRequestBase) request).getEntity());
        }

        return request.getRequestLine().getMethod() + "\n"
                + canonicalUrl + "\n"
                + timestamp + "\n"
                + nonce + "\n"
                + body + "\n";
    }


}